# coding=utf-8

##############################################################################
#
# pluginmanager.py
# A classes to handle plugins
#
# Copyright (c) 2007 Webcore Corp. All Rights Reserved.
#
##############################################################################
""" pluginmanager.py - A classes to handle plugins

$Id: pluginmanager.py 18 2011-09-28 07:32:07Z shib.ats $
"""

__author__  = 'Atsushi Shibata <shibata@webcore.co.jp>'
__docformat__ = 'plaintext'
__licence__ = 'MIT'

import os
import logging
from inspect import isclass
from copy import copy
import shelve
from utilities import exp2log

from pluginbase import BaseFeeder, BaseProcessor, BasePublisher
from utilities import seek_files, print_exc, get_var_path

class BasePluginManager(object):
    """
    PluginをハンドルするクラスのBaseとなるクラス
    
    1) プラグインの読み込み
    インスタンス生成時に，ディレクトリを引数に指定して，プラグインとなるクラスを読み込む
    あらかじめプラグインとして扱うベースクラスを指定しておき，サブクラスのみを登録する
    
    2) プラグインインスタンスの生成
    プラグインの実体となるインスタンスは，文字列を一意のIDとして管理する
    IDとインスタンス生成時に利用する設定辞書をペアにした辞書を渡して
    インスタンスを生成する
    生成したプラグインのインスタンスには，IDの文字列を使って辞書風にアクセスできる
    """
    
    # Pluginとして扱うベースクラスのリスト
    _baseclasses = (object,)

    def __init__(self, plugindir='plugins'):
        """
        初期化メソッド
        
        base : Pluginを読み込むディレクトリ
        """
        # クラスの名前とクラスオブジェクトの辞書を作る
        self._plugin_classes = {}
        [self._plugin_classes.update({x.__name__:x})
                                for x in self.load_plugins(plugindir)]
        # Pluginインスタンスの辞書を初期化
        self._plugins = {}

    def load_plugins(self, base):
        """
        Pluginを読み込み，_baseclassesにあるクラスのサブクラスのみを抽出し
        クラスオブジェクトのリストを返す
        """
        
        ret_list = []
        plugin_path = seek_files(base, basepath=os.path.dirname(__file__))
        for p in plugin_path:
            mname = os.path.split(p)
            imppath = '.'.join(mname).replace(os.path.sep, '.')
            # モジュール/パッケージを読み込む
            m = __import__(imppath, globals(), globals(), mname[-1])
            # モジュール/バッケージのグローバルにあるnameの一覧を得る
            names = dir(m)
            if hasattr(m, '__all__'):
                # __all__が定義されていたのでnamesを制限する
                names = m.__all__
            for n in names:
                obj = getattr(m, n)
                if isclass(obj) and issubclass(obj, self._baseclasses):
                    # クラスを追加する
                    ret_list.append(obj)
                    logging.debug("""%s : loading plugin '%s'""" % \
                                        (self.__class__.__name__, n))
        return ret_list

    def populate_plugins(self, settings, **kws):
        """
        プラグインのクラスインスタンスをインスタンシエートする
        プラグインID，インスタンスを値に持つ辞書を返す
        settings : プラグインのIDをキー，設定用辞書を値に持つ辞書
                   classというキーが必須，クラス名(プラグイン名)を渡す
        """
        p_d = self._plugins
        for k in settings.keys():
            if 'classname' not in settings[k].keys():
                raise KeyError(
                    """Setting for %s should have a key 'classname'""" % k)
            d = copy(settings[k])
            d['id'] = k
            classname = d['classname']
            del d['classname']
            # クラスインスタンスを作る
            try:
                ins = self._plugin_classes[classname](d, **kws)
                p_d[k] = ins
                ins.set_id(k)
                logging.debug("""%s : making instance %s for '%s'""" % \
                                    (self.__class__.__name__, classname, k))
            except:
                exp2log()

    def __iter__(self):
        """
        生成済みのPluginのIDを返すイテレータを返す
        """
        for p in self._plugins.keys():
            yield p

    def __getitem__(self, key):
        """
        生成済みのPluginのオブジェクトを返す
        """
        return self._plugins[key]

    def __setitem__(self, key, value):
        """
        生成済みのPluginのオブジェクトにアクセスするためのメソッド
        """
        self._plugins[key] = value


class PluginManagerWithShelve(BasePluginManager):
    """
    Pluginを登録，管理するクラス，個々のPluginの設定としてshelveを渡す
    """
    # Pluginとして扱うベースクラスのリスト
    _dataext = '.dat'

    def __init__(self, base, datadir=None):
        """
        初期化メソッド
        
        datadir  : プラグインのデータを保存するディレクトリ(オプション)
        """
        BasePluginManager.__init__(self, base)
        if not datadir:
            datadir = get_var_path()
        self.datadir = datadir
        if not os.path.exists(self.datadir):
            # data保存用のディレクトリがないので作る
            os.mkdir(self.datadir)

    def _get_shelve(self, plugin_id, writeback=True):
        """
        shelveを開いて返す
        
        plugin_id : PluginのID，このIDをもとにファイルを開く
        """
        # 設定を保存するshelveを開く
        logging.info(os.path.join(self.datadir, plugin_id+self._dataext))
        s = shelve.open(os.path.join(self.datadir, plugin_id+self._dataext),
                        writeback=writeback)
        return s

class FeederManager(PluginManagerWithShelve):
    """
    Feeder Pluginを登録，管理するクラス
    """
    # Pluginとして扱うベースクラスのリスト
    _baseclasses = (BaseFeeder,)
    _dataext = '4feeder.dat'

    def __init__(self, base, settings, datadir=None):
        """
        初期化メソッド
        
        base     : プラグインを読み込むベースとなるpath
        settings : プラグインを生成するときに利用する辞書
        datadir  : プラグインのデータを保存するディレクトリ(オプション)
        """
        PluginManagerWithShelve.__init__(self, base, datadir)
        self.populate_plugins(settings)

    def feed_all(self):
        """
        生成済みのプラグインすべてに対してfeedして結果を返す
        返り値となるのは辞書のリスト
        """
        ret_l = []
        for k in self:
            l = self.feed(k)
            ret_l += list(self.feed(k))
        return ret_l

    def feed(self, plugin_id):
        """
        生成済みの特定IDを持つプラグインに対してfeedして結果を返す
        返り値となるのは辞書のリスト
        
        plugin_id : プラグインのID
        """
        ret_l = []
        # 設定を保存するshelveを開く
        s = self._get_shelve(plugin_id)
        ins = self[plugin_id]
        if ins.can_feed(s):
            try:
                self[plugin_id].before_feed(s)
                ret_l = self[plugin_id].feed(s)
                self[plugin_id].after_feed(s)
                return ret_l
            except:
                exp2log(logging.error)
        return []


class ProcessorManager(PluginManagerWithShelve):
    """
    Processor Pluginを登録，管理するクラス
    """
    # Pluginとして扱うベースクラスのリスト
    _baseclasses = (BaseProcessor,)
    _dataext = '4processor.dat'

    def __init__(self, base, settings, feeders, publishers, datadir=None):
        """
        初期化メソッド
        
        base     : プラグインを読み込むベースとなるpath
        settings : プラグインを生成するときに利用する辞書
        datadir  : プラグインのデータを保存するディレクトリ(オプション)
        """
        PluginManagerWithShelve.__init__(self, base, datadir)
        self.populate_plugins(settings, feeders=feeders,
                                publishers=publishers)

    def process(self, plugin_id, feedermgr, publishermgr):
        """
        生成済みの特定IDを持つプラグインに対してprocessして結果を返す
        
        plugin_id : プラグインのID
        """
        ret_l = []
        # 設定を保存するshelveを開く
        s = self._get_shelve(plugin_id)
        try:
            self[plugin_id].process(s, feedermgr, publishermgr)
        except:
            exp2log(logging.error)


class PublisherManager(PluginManagerWithShelve):
    """
    Publisher Pluginを登録，管理するクラス
    """
    # Pluginとして扱うベースクラスのリスト
    _baseclasses = (BasePublisher,)
    _dataext = '4publisher.dat'

    def __init__(self, base, settings, datadir=None):
        """
        初期化メソッド
        
        base     : プラグインを読み込むベースとなるpath
        settings : プラグインを生成するときに利用する辞書
        datadir  : プラグインのデータを保存するディレクトリ(オプション)
        """
        PluginManagerWithShelve.__init__(self, base, datadir)
        self.populate_plugins(settings)

    def publish(self, plugin_id, item):
        """
        生成済みの特定IDを持つプラグインに対してpublishして結果を返す
        
        plugin_id : プラグインのID
        """
        ret_l = []
        # 設定を保存するshelveを開く
        s = self._get_shelve(plugin_id)
        try:
            self[plugin_id].publish(s, item)
        except:
            exp2log(logging.error)

    def finish_publish(self, plugin_id):
        """
        生成済みの特定IDを持つプラグインに対してpublish後の処理を呼び出す
        
        plugin_id : プラグインのID
        """
        ret_l = []
        # 設定を保存するshelveを開く
        try:
            self[plugin_id].finish_publish()
        except:
            exp2log(logging.error)


    def test_publish(self, plugin_id, item):
        """
        生成済みの特定IDを持つプラグインに対してpublishのテストを行う
        
        plugin_id : プラグインのID
        """
        ret_l = []
        # 設定を保存するshelveを開く
        s = self._get_shelve(plugin_id)
        r = self[plugin_id].test_publish(s, item)
        if not r:
            r = self[plugin_id].publish(s, item)
        return r

